package cc.shacocloud.mirage.utils.annotation;

import cc.shacocloud.mirage.utils.map.ConcurrentReferenceHashMap;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 提供一种快速访问具有一致顺序的 {@link Annotation} 的属性方法的方法以及一些有用的实用程序方法。
 */
final class AttributeMethods implements Iterable<AttributeMethod> {
    
    static final AttributeMethods NONE = new AttributeMethods(null, new Method[0]);
    
    private static final Map<Class<? extends Annotation>, AttributeMethods> cache = new ConcurrentReferenceHashMap<>();
    
    private static final Comparator<Method> methodComparator = (m1, m2) -> {
        if (m1 != null && m2 != null) {
            return m1.getName().compareTo(m2.getName());
        }
        return m1 != null ? -1 : 1;
    };
    
    @Getter
    @Nullable
    private final Class<? extends Annotation> annotationType;
    
    private final List<AttributeMethod> attributeMethods;
    
    private AttributeMethods(@Nullable Class<? extends Annotation> annotationType, Method @NotNull [] methods) {
        this.annotationType = annotationType;
        this.attributeMethods = Arrays.stream(methods)
                .map(method -> new AttributeMethod(this, method))
                .collect(Collectors.toList());
    }
    
    /**
     * 获取给定批注类型的属性方法
     *
     * @param annotationType {@link Annotation}
     * @return 注释类型的属性方法
     */
    static AttributeMethods forAnnotationType(@Nullable Class<? extends Annotation> annotationType) {
        if (annotationType == null) {
            return NONE;
        }
        
        return cache.computeIfAbsent(annotationType, AttributeMethods::compute);
    }
    
    private static AttributeMethods compute(@NotNull Class<? extends Annotation> annotationType) {
        Method[] methods = annotationType.getDeclaredMethods();
        int size = methods.length;
        for (int i = 0; i < methods.length; i++) {
            if (!isAttributeMethod(methods[i])) {
                methods[i] = null;
                size--;
            }
        }
        if (size == 0) {
            return NONE;
        }
        Arrays.sort(methods, methodComparator);
        Method[] attributeMethods = Arrays.copyOf(methods, size);
        return new AttributeMethods(annotationType, attributeMethods);
    }
    
    private static boolean isAttributeMethod(@NotNull Method method) {
        return (method.getParameterCount() == 0 && method.getReturnType() != void.class);
    }
    
    /**
     * 获取具有指定名称的属性，如果不存在匹配的属性，则获取 {@code null}
     *
     * @param name 要查找的属性名称
     * @return 属性方法或 {@code null}
     */
    @Nullable
    AttributeMethod get(String name) {
        int index = indexOf(name);
        return index != -1 ? get(index) : null;
    }
    
    /**
     * 获取指定索引处的属性
     *
     * @param index 要返回的属性的索引
     * @return 属性方法
     * @throws IndexOutOfBoundsException 如果索引超出范围则抛出
     */
    AttributeMethod get(int index) {
        return this.attributeMethods.get(index);
    }
    
    /**
     * 获取具有指定名称的属性的索引，如果没有具有该名称的属性，则获取 {@code -1}
     *
     * @param name 要查找的名称
     * @return 属性的索引，或 {@code -1}
     */
    int indexOf(String name) {
        int size = size();
        for (int i = 0; i < size; i++) {
            if (this.attributeMethods.get(i).getName().equals(name)) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * 获取指定属性的索引，如果属性不在此集合中，则获取 {@code -1}
     *
     * @param attribute 要查找的属性
     * @return 属性的索引，或 {@code -1}
     */
    int indexOf(Method attribute) {
        int size = size();
        for (int i = 0; i < size; i++) {
            if (this.attributeMethods.get(i).getSourceMethod().equals(attribute)) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * 获取此集合中的属性数
     *
     * @return 属性的数量
     */
    int size() {
        return this.attributeMethods.size();
    }
    
    @NotNull
    @Override
    public Iterator<AttributeMethod> iterator() {
        return attributeMethods.iterator();
    }
}
